package com.tbfeng.id.builder.service.impl;

import com.tbfeng.id.builder.domian.Segment;
import com.tbfeng.id.builder.service.IDGen;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @author linjf48556
 * @Description
 * @date 2024-03-12
 **/
@Slf4j
@Component
public class SegmentIDGenImpl implements IDGen {

    @Autowired
    private PubSeqService segmentService;

    private final LockContainer lockContainer = new LockContainer();
    private final Map<String, Sequence> sequenceMap = new HashMap<>(8);

    public Long get(String bizTag) {
        long id;
        Object lock = lockContainer.getLock(bizTag);
        synchronized (lock) {
            Sequence sequence = sequenceMap.get(bizTag);
            if (sequence == null) {
                Segment segment = segmentService.getSegment(bizTag);
                sequence = new Sequence(segment.getMaxId(), segment.getStep());
                sequenceMap.put(bizTag, sequence);
            }
            id = sequence.nextId();
            if (sequence.isEmpty()) {
                sequenceMap.remove(bizTag);
            }
        }
        return id;
    }

    @Data
    class Sequence {
        /**
         * 当前已经使用的id
         */
        private long id;
        /**
         * 当前序列范围中最大可用id
         */
        private final long maxId;

        Sequence(Long id, Integer step) {
            this.id = id;
            this.maxId = id + step;
        }

        long nextId() {
            return ++id;
        }

        boolean isEmpty() {
            return id >= maxId;
        }

    }

    /**
     * 锁容器
     */
    class LockContainer {

        private final Map<String, Object> locks = new HashMap<>(8);

        /**
         * 通过 key 获取一把固定的锁
         *
         * @param key
         * @return
         */
        public Object getLock(String key) {
            Object lock = locks.get(key);
            if (lock == null) {
                synchronized (locks) {
                    lock = locks.get(key);
                    if (lock == null) {
                        lock = new Object();
                        locks.put(key, lock);
                    }
                }
            }
            return lock;
        }

    }
}
